/**
 * @desc index
 * @author DragonTeam <https://www.bpmport.com>
 * @since 2024/10/31 16:13
 */

/* bpmn 相关转换内容 */
import { transformBpmnUserTask } from './element-properties/user-task'
import { transformBpmnServiceTask } from './element-properties/service-task'
import { moddle } from '@/bpmn-moddle'
import type { BpmnDefinitions, BpmnElement, BpmnFlow, BpmnRoot } from '@/types/bpmn-node'
import type { BaseNode, BaseNodeBO, GatewayNode, SubprocessNode } from '@/types'
import { capitalize } from '@/utils/tools'
import { createElement } from '@/data-transform/bpmn-transform/utils'

type BpmnProps = Record<string, any>

/* 1. bpmn 元素创建 */
export function createBpmnDefinitions() {
  const defs = createElement<BpmnDefinitions>('bpmn:Definitions')
  defs.rootElements = []
  return defs
}
export function createBpmnProcess(props: BpmnProps) {
  const process = createElement<BpmnRoot>('bpmn:Process', { ...props })
  process.flowElements = []
  return process
}
export function createBpmnSubprocess(props: BpmnProps) {
  const process = createElement<BpmnRoot>('bpmn:SubProcess', { ...props })
  process.flowElements = []
  return process
}
export function createBpmnElement(type: string, props: BpmnProps, nodeBo?: BaseNodeBO) {
  const element = createElement<BpmnElement>(`bpmn:${capitalize(type)}`, props)
  if (!nodeBo)
    return element
  if (type === 'userTask') {
    transformBpmnUserTask(nodeBo, element)
  }
  if (type === 'serviceTask') {
    transformBpmnServiceTask(nodeBo, element)
  }
  return element
}
export function createBpmnSequenceFlow(source: BpmnElement, target: BpmnElement, props: BpmnProps = {}) {
  const { id, name, expression } = props
  const flow = createElement<BpmnFlow>(`bpmn:SequenceFlow`, { id, name })
  flow.sourceRef = source
  flow.targetRef = target

  if (expression) {
    flow.conditionExpression = createElement(`bpmn:FormalExpression`, { body: expression })
  }

  return flow
}

/* 2. 单个节点解析 */
const parsedElementIds = new Set<string>()
const parsedElementMaps = new Map<string, BpmnElement>()

function prependSequenceFlow(node: BaseNode, nodeElement: BpmnElement, parentArray: BpmnElement[]) {
  if (node.$prev && parsedElementMaps.get(node.$prev.id)) {
    parentArray.push(createBpmnSequenceFlow(parsedElementMaps.get(node.$prev.id)!, nodeElement))
  }
}

function transformNode(node: BaseNode, parentArray: BpmnElement[], createFow: boolean = true) {
  if (!node || parsedElementIds.has(node.id))
    return

  const { type, id } = node

  let current: BpmnElement
  if (type === 'subprocess') {
    current = transformSubprocessNode(node as SubprocessNode, parentArray, createFow)
  }
  else if (type === 'gateway') {
    current = transformGatewayNode(node as GatewayNode, parentArray, createFow)
  }
  else {
    current = transformCommonNode(node, parentArray, createFow)
  }

  parsedElementIds.add(id)
  parsedElementMaps.set(id, current)

  return current
}

function transformCommonNode(node: BaseNode, parentArray: BpmnElement[], createFow: boolean = true): BpmnElement {
  const { id, name, businessData } = node
  const element = createBpmnElement(businessData.$type!, { id, name }, businessData)
  if (createFow) {
    prependSequenceFlow(node, element, parentArray)
  }
  return element
}

function transformSubprocessNode(node: SubprocessNode, parentArray: BpmnElement[], createFow: boolean = true): BpmnRoot {
  const { id, name, $start } = node
  const subprocess = createBpmnSubprocess({ id, name })

  let cur: BaseNode | undefined = $start
  while (cur) {
    if (cur.type === 'subprocess') {
      subprocess.flowElements.push(transformSubprocessNode(cur as SubprocessNode, subprocess.flowElements))
    }
    else {
      const element = transformNode(cur, subprocess.flowElements)
      if (element) {
        subprocess.flowElements.push(element)
      }
    }
    cur = cur.$next
  }

  if (createFow) {
    prependSequenceFlow(node, subprocess, parentArray)
  }

  return subprocess
}

/*
* 网关节点，通过生成前置网关节点和后置网关聚合节点，将整个网关作为整体处理，返回聚合网关作为后续处理节点
*              -- exp1 --
* ~ gateway --|-- exp2 --|-- aggregation ~
*              -- exp3 --
* */
function transformGatewayNode(node: GatewayNode, parentArray: BpmnElement[], createFow: boolean = true): BpmnElement {
  const gateway = createBpmnElement(node.businessData.$type!, { id: node.id, name: node.businessData.name })
  const aggregationGateway = createBpmnElement(node.businessData.$type!, { id: `${node.id}-aggregation`, name: `聚合网关-${node.id}` })

  if (createFow) {
    prependSequenceFlow(node, gateway, parentArray)
  }

  parentArray.push(gateway)

  // 遍历连线处理
  for (const exp of node.$expressions) {
    // 条件会转为连线，所以直接判断条件节点的后一个节点
    const expNext: BaseNode | undefined = exp.$next

    // 如果没有节点，直接连接网关和 aggregationGateway
    if (!expNext) {
      const flow = createBpmnSequenceFlow(gateway, aggregationGateway, exp)
      parentArray.push(flow)

      // 默认路径判断
      if (node.$default?.id === exp.id) {
        gateway.set('default', flow)
      }
    }

    // 存在其他节点，则处理第一节点，然后按常规遍历方式遍历后续
    else {
      const nextElement = transformNode(expNext, parentArray, false)!
      // 单独创建首根条件路径
      const flow = createBpmnSequenceFlow(gateway, nextElement, exp)
      // 需要手动缓存和插入到 bpmn 树
      parentArray.push(nextElement, flow)
      parsedElementIds.add(expNext.id)
      parsedElementMaps.set(expNext.id, nextElement)

      // 默认路径判断
      if (node.$default?.id === exp.id) {
        gateway.set('default', flow)
      }

      let cur: BaseNode | undefined = expNext.$next
      let lastElement: BpmnElement = nextElement
      while (cur) {
        const element = transformNode(cur, parentArray)
        if (element) {
          parentArray.push(element)
          lastElement = element
        }
        cur = cur.$next
      }

      // 最后创建该条件链路最后一个元素与聚合网关之间的连线
      const lastFlow = createBpmnSequenceFlow(lastElement, aggregationGateway)
      parentArray.push(lastFlow)
    }
  }

  return aggregationGateway
}

/* 3. 遍历节点解析生成xml */
export async function transformToXML(node: BaseNode): Promise<{ xml: string }> {
  parsedElementIds.clear()
  parsedElementMaps.clear()

  const definitions = createBpmnDefinitions()
  const process = createBpmnProcess({})
  definitions.rootElements = [process]

  let cur: BaseNode | undefined = node
  while (cur) {
    const element = transformNode(cur, process.flowElements)
    if (element) {
      process.flowElements.push(element)
    }
    cur = cur.$next
  }

  return await moddle.toXML(definitions, { format: true, preamble: true })
}
